/**
 * Copyright (c) 2023 murenchao
 * taomu is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *       http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */
package cool.taomu.box.guice

import com.google.inject.Binder
import com.google.inject.Provider
import com.google.inject.binder.AnnotatedBindingBuilder
import com.google.inject.binder.LinkedBindingBuilder
import com.google.inject.binder.ScopedBindingBuilder
import com.google.inject.name.Names
import cool.taomu.box.asm.CreateClass
import cool.taomu.box.asm.CreateMethod
import cool.taomu.box.asm.entity.ClassEntity
import cool.taomu.box.asm.entity.MethodEntity
import cool.taomu.box.guice.entity.BinderEntity
import cool.taomu.box.guice.entity.Scope
import java.lang.annotation.Annotation
import java.util.ArrayList
import java.util.HashMap
import java.util.List
import java.util.UUID
import org.objectweb.asm.Type

class BinderRule {
	def static toBinderEntity(cool.taomu.box.guice.ann.Binder[] binders) {
		val results = new ArrayList<BinderEntity>(binders.size);
		binders.filterNull.forEach [
			var entity = new BinderEntity;
			entity.provider = it.provider;
			entity.in = it.in;
			entity.value = it.value;
			if (!it.named.nullOrEmpty) {
				entity.named = it.named;
			}
			if (it.bind !== null) {
				entity.bind = it.bind;
			}
			if (it.to !== null && it.to !== Void) {
				entity.to = it.to;
				if (it.to.interfaces.findFirst [
					it.equals(Provider) || it.equals(javax.inject.Provider)
				] !== null) {
					entity.provider = true;
				}
			}

			results.add(entity);
		]
		return results;
	}

	def static void rule(CreateMethod cm, List<BinderEntity> binders, HashMap<String, ClassLoader> ccloader) {
		val bind = new MethodEntity(Binder,Binder.getMethod("bind",Class));
		val to = new MethodEntity(AnnotatedBindingBuilder,AnnotatedBindingBuilder.getMethod("to",Class));
		val toInstance = new MethodEntity(AnnotatedBindingBuilder, AnnotatedBindingBuilder.getMethod("toInstance",Object))
		val toInstance2 = new MethodEntity(LinkedBindingBuilder,LinkedBindingBuilder.getMethod("toInstance",Object));
		val toProvider = new MethodEntity(AnnotatedBindingBuilder,AnnotatedBindingBuilder.getMethod("toProvider",Class))
		val in = new MethodEntity(ScopedBindingBuilder,ScopedBindingBuilder.getMethod("in",com.google.inject.Scope));
		binders.filterNull.forEach [
			if (it.bind !== null) {
				cm.load("binder").ldc(Type.getType(it.bind)).invokeInterface(bind)
				if (it.named !== null) {
					cm.ldc(it.named);
					cm.invokeStatic(new MethodEntity(Names,Names.getMethod("named",String)))
					cm.invokeInterface(
						new MethodEntity(AnnotatedBindingBuilder,AnnotatedBindingBuilder.getMethod("annotatedWith",Annotation)));
					constToInstance(it, cm, toInstance2)
				} else {
					constToInstance(it, cm, toInstance)
				}
				if (!it.to.equals(Void)) {
					if (it.provider) {
						cm.ldc(Type.getType(it.to)).invokeInterface(toProvider);
					} else {
						if (it.bind.equals(it.to) && !it.bind.isInterface) {
							var ccc = new ClassEntity("cool.taomu.box.tmp",
								#["Taomu", UUID.randomUUID.toString.replace("-", ""), "Impl"].join(""));
							ccc.superclass = it.to.name.replace(".","/");
							var c = CreateClass.Class(ccc);
							c.constructor(it.to.constructors);
							c.end();
							var name = #[ccc.packageName, ccc.className].join(".");
							ccloader.put(name,c);
							cm.ldc(name);
							cm.invokeStatic(
								new MethodEntity(TaomuGuiceModule.name.replace(".", "/"),
									"java/lang/Class loadZlass(java/lang/String)", true));
							cm.invokeInterface(to);
						} else {
							cm.ldc(Type.getType(it.to)).invokeInterface(to);
						}
					}
					if (it.in !== Scope.None) {
						if (it.in.equals(Scope.SINGLETON)) {
							cm.getStatic("com/google/inject/Scopes", "SINGLETON", "Lcom/google/inject/Scope;");
						} else if (it.in.equals(Scope.NO_SCOPE)) {
							cm.getStatic("com/google/inject/Scopes", "NO_SCOPE", "Lcom/google/inject/Scope;");
						}
						cm.invokeInterface(in);
					} else {
						cm.pop
					}
				}
				if (it.named === null && it.to.equals(Void)) {
					cm.pop
				}
			}
		]
	}

	protected def static CreateMethod constToInstance(BinderEntity it, CreateMethod cm, MethodEntity toInstance) {
		switch (it.bind) {
			case int: {
				cm.ldc(it.value)
				cm.invokeStatic(Integer, "int parseInt(java/lang/String)")
				cm.invokeStatic(Integer, "java/lang/Integer valueOf(int)")
				cm.invokeInterface(toInstance)
			}
			case long: {
				cm.ldc(it.value)
				cm.invokeStatic(Long, "long parseLong(java/lang/String)")
				cm.invokeStatic(Long, "java/lang/Long valueOf(long)")
				cm.invokeInterface(toInstance)
			}
			case short: {
				cm.ldc(it.value)
				cm.invokeStatic(Short, "short parseShort(java/lang/String)")
				cm.invokeStatic(Short, "java/lang/Short valueOf(short)")
				cm.invokeInterface(toInstance)
			}
			case double: {
				cm.ldc(it.value)
				cm.invokeStatic(Double, "double parseDouble(java/lang/String)")
				cm.invokeStatic(Double, "java/lang/Double valueOf(double)")
				cm.invokeInterface(toInstance)
			}
			case float: {
				cm.ldc(it.value)
				cm.invokeStatic(Float, "float parseFloat(java/lang/String)")
				cm.invokeStatic(Float, "java/lang/Float valueOf(float)")
				cm.invokeInterface(toInstance)
			}
			case String: {
				cm.ldc(it.value)
				cm.invokeInterface(toInstance)
			}
			default: {
			}
		}
	}
}
